package ru.amse.jsynchro.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Socket;

import ru.amse.jsynchro.GUI.IStatusObserver;
import ru.amse.jsynchro.fileSystem.FileSystem;
import ru.amse.jsynchro.fileSystem.LocalFileSystem;
import ru.amse.jsynchro.fileSystem.RemoteFileSystem;
import ru.amse.jsynchro.kernel.task.HistoryTask;
import ru.amse.jsynchro.logging.ILogger;
import ru.amse.jsynchro.logging.LogFactory;

public class Client extends Thread {
    public static final int START = 0;
    public static final int UPDATE = 1;
    public static final int UPLOAD = 2;
    
    private String folderName;
    public int port = 12345;
    private String serverHost;
    private String userName;
    private char[] userPassword;
    private long updatePeriod = 15000;
    
    private volatile boolean running = false;
    
    private FileSystem fs;
    private Socket clientSocket;
    private InputStream socketIn;
    private OutputStream socketOut;
    private int connectionAttempt = 20;
    private int threadNo = 0;
    private HistoryTask task;
    private RemoteFileSystem remoteFS;
    private ILogger logger;
    private IStatusObserver stObserver;
    private int threadType;
    
    
    public Client(String folderName, String userName, 
            char[] userPassword, String serverHost, 
            int port, ILogger logger, IStatusObserver stObserver) {
        this.logger = logger;
        this.folderName = folderName; 
        this.userName = userName;
        this.userPassword = userPassword;
        this.serverHost = serverHost;
        this.port = port;
        if (stObserver == null) {
            // to void NPE
            stObserver = new IStatusObserver() {
                public void setState(String state) {}
            };
        }
        this.stObserver = stObserver;
        fs = new LocalFileSystem();
        try {
            task  = new HistoryTask(logger);
        } catch (IOException e) {
            e.printStackTrace();
        }
    } 
    
    //version for tests
    Client(String folderName, String userName, String userPassword, int threadNo, int port) {
        this(folderName, userName, userPassword.toCharArray(), "localhost", 
                port, LogFactory.getLocalLogger(true, LogFactory.CONSOLE_LOG), null);
        this.threadNo = threadNo;
    }
    
    public void setThreadType(int type) {
        this.threadType = type;
    }
    
    public void run() {
        switch (threadType) {
        case START: startThread();break;
        case UPDATE: updateThread();break;
        case UPLOAD: uploadThread();break;
        }
        
    }
    
    private void startThread() {
        running = true;
        while (running) {
            try {
                stObserver.setState("connecting");
                if (!connectToServer()) {
                    logger.info("can't connect");
                    stObserver.setState("ready");
                    return;
                }
                if (!authenticate()) {
                    logger.info("can't auth");
                    stObserver.setState("ready");
                    return;
                }
                stObserver.setState("analyzing");
                logger.info("analyzing");
                analyze();
                
                logger.info("commiting");
                stObserver.setState("committing");
                task.commit();
                
                logger.info("updating");
                stObserver.setState("updating");
                task.update();
                logger.info("wating");
                stObserver.setState("waiting");
                disconnectFromServer();
                logger.info("disconnected");
                try {
                    sleep(updatePeriod);
                } catch (InterruptedException e) {
                    logger.exception(e);
                }
            } catch (IOException e) {
                logger.exception(e);
            }
        }
    }
    
    public void off() {
        if (running) {
            running = false;
        }
    }
    
    public void analyze() throws IOException {
        logger.info("thread " + threadNo + "in analyze()");
        String serverFolder = remoteFS.getTreeRoot();
        task.analyze(folderName, fs, serverFolder, remoteFS);
    }
    
    private void updateThread() {
        logger.info("updating");
        System.out.println(stObserver);
        stObserver.setState("connecting");
        if (!connectToServer()) {
            logger.info("can't connect");
            stObserver.setState("ready");
            return;
        }
        if (!authenticate()) {
            logger.info("can't auth");
            stObserver.setState("ready");
            return;
        }
        stObserver.setState("analyzing");
        logger.info("analyzing");
        String serverFolder;
        try {
            serverFolder = remoteFS.getTreeRoot();
            task.analyzeRemote(folderName, fs, serverFolder, remoteFS);
            stObserver.setState("transferring data");
            task.update();
        } catch (IOException e) {
            logger.exception(e);
        }
        disconnectFromServer();
        stObserver.setState("ready");
    }
    
    public boolean connectToServer() {
        boolean waitForConnection = true;
        while (waitForConnection) {
            logger.info("client " + threadNo + " trying to connect to the server " 
                    + serverHost + " " + port +  " (" + connectionAttempt 
                    + " attempts remain)");
            try {
                clientSocket = new Socket(serverHost, port);
                logger.info("client Socket created");
                System.out.println(clientSocket.getSoTimeout());
                socketIn = clientSocket.getInputStream();
                socketOut = clientSocket.getOutputStream();
                remoteFS = new RemoteFileSystem(socketIn, socketOut, logger);
                waitForConnection = false;
            } catch (ConnectException e) {
                logger.info(e.getMessage());
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException ie) {
//                }
            } catch (Exception e) { //SocketException, UnknownHostException, IOException
                logger.exception(e);
            } finally {
                if (--connectionAttempt == 0) {
                    return false;
                }
            }
        }
        return true;
    }
    
    public boolean authenticate() {
        try {
            String password = new String(userPassword);
            if (remoteFS.auth(userName, password) == 1) {
                logger.info("authenticated with name " + userName
                        + "and password " + password);
                return true;
            }
        } catch (IOException e) {
            logger.exception(e);
        }
        return false;
    }
    
    
//    private void getLocalChanges() {
//        
//    }
    
    private void uploadThread() {
        logger.info("uploading");
        stObserver.setState("connecting");
        if (!connectToServer()) {
            logger.info("can't connect");
            stObserver.setState("ready");
            return;
        }
        if (!authenticate()) {
            logger.info("can't auth");
            stObserver.setState("ready");
            return;
        }
        stObserver.setState("analyzing");
        logger.info("analyzing");
        String serverFolder;
        try {
            serverFolder = remoteFS.getTreeRoot();
            task.analyzeUpload(folderName, fs, serverFolder, remoteFS);
            stObserver.setState("transferring data");
            task.upload();
        } catch (IOException e) {
            logger.exception(e);
        }
        disconnectFromServer();
        stObserver.setState("ready");
    }

    public String getFolderName() {
        return folderName;
    }

    public void setFolderName(String folderName) {
        this.folderName = folderName;
    }

    public String getHost() {
        return serverHost;
    }

    public void setHost(String host) {
        this.serverHost = host;
    }
    
    public void disconnectFromServer() {
        try {
            remoteFS.closeSession();
        } catch (IOException e) {
            logger.exception(e);
        }
    }
}
